/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2020年9月24日
 */
package com.massyframework.beanskin.runtime.framework.installing.assembly;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.util.IOUtils;
import com.massyframework.beanskin.util.scanning.JarFileResourceScanner;
import com.massyframework.beanskin.util.scanning.ResourceScanner;
import com.massyframework.beanskin.util.scanning.ResourceScanningCallback;


/**
 * @author huangkh
 *
 */
public class ResourcesExtractor {
	
	private static final String PATH = "BEANSKIN-INF/resources";
	private Logger logger;
	private List<ResourceScanner<?>> scanners;

	/**
	 * 构造方法
	 */
	public ResourcesExtractor() {
		this.logger = LoggerFactory.getLogger(ResourcesExtractor.class.getName());
		this.scanners = new ArrayList<ResourceScanner<?>>();
		this.scanners.add(new JarFileResourceScanner());
		this.scanners.add(new TargetClassesResourceScanner());
	}

	/**
	 * 提取META-INF/massy/assembly/resource目录下文件并复制到{@code outputDir}目录下
	 * @param classLoader {@link ClassLoader},作为扫描参考路径的url.
	 * @param outputDir {@link File}, 提取复制输出目录
	 * @return {@link List}, 复制文件的URL列表
	 */
	public List<URL> extract(ClassLoader classLoader, File outputDir){
		Callback callback = new Callback(outputDir, this.logger);	
		
		try {
		Enumeration<URL> em = classLoader.getResources(PATH);
		while (em.hasMoreElements()) {
			URL url = em.nextElement();
			for (ResourceScanner<?> scanner: this.scanners) {
				this.extract0(url, PATH, scanner, callback);
			}
		}	
		}catch(Exception e) {
			if (logger.isErrorEnabled()) {
				logger.error(e.getMessage(), e);
			}
		}
		return callback.getTargetUrls();
	}
	
	/**
	 * 判断{@code scanner}是否支持资源的扫描，如果支持则调用{@code callback}返回扫描结果
	 * @param <T>  
	 * @param url {@link URL},参考路径的url.
	 * @param libPath {@link String}, 相对路径
	 * @param scanner {@link ResourceScanner}，扫描器
	 * @param callback {@link ScanCallback}, 回调方法
	 */
	protected <T> void extract0(URL url, String path, 
			ResourceScanner<T> scanner, ResourceScanningCallback callback) {
		T instance = scanner.support(url);
		if (instance != null) {
			scanner.scanning(instance, path, (fileName) -> true, true, callback);
		}
	}
		
	private class Callback implements ResourceScanningCallback{
		private final File outputDir;
		private final Logger logger;
		private List<URL> urls;
		
		public Callback(File outputDir, Logger logger) {
			this.outputDir = Objects.requireNonNull(outputDir, "\"outputDir\" cannot be null.");
			if (this.outputDir.isFile()) {
				throw new IllegalArgumentException("\"outputDir\" cannot be a file.");
			}
			
			if (!this.outputDir.exists()) {
				this.outputDir.mkdirs();
			}
			
			this.logger = Objects.requireNonNull(logger, "\"logger\" can；not be null.");
			this.urls = new ArrayList<URL>();
		}

		@Override
		public void onFound(String path, URL resource) {
			if (logger.isTraceEnabled()) {
				logger.trace(String.format("found embed resource : \r\n\tname=%s , \r\n\turl= %s .", path, resource));
			}
			if (path.endsWith("/")) {
				return;
			}
			
			File outputFile = new File(outputDir, path);
			if (!outputFile.exists()) {
				File dir = outputFile.getParentFile();
				if (!dir.exists()) {
					dir.mkdirs();
				}
				InputStream input = null;
				try {
					input = resource.openStream();
					Path target = outputFile.toPath();
					Files.copy(input, target);
					this.urls.add(target.toUri().toURL());
				} catch (IOException e) {
					if (logger.isErrorEnabled()) {
						logger.error(e.getMessage(), e);
					}
				} finally {
					IOUtils.close(input);
				}
			}else{
				try {
					this.urls.add(outputFile.toURI().toURL());
				}catch(Exception e){

				}
			}
		}
		
		public List<URL> getTargetUrls(){
			return this.urls;
		}
	}

}
